﻿/*  (c) 2008 Benjamin Schroeter
 *  This file is part of JaDAL - Just another DSL-Tools Addon Library
 *  and licensed under the New BSD License
 *  For more information please visit http://www.codeplex.com/JaDAL */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;

namespace BenjaminSchroeter.Dsl.UserRestrictions
{
    /// <summary>
    /// This provider adds the functionality of dynamically restricted
    /// properties to ModelElements.
    /// </summary>
    public class UserRestrictionProvider : TypeDescriptionProvider
    {
        private readonly TypeDescriptionProvider parent;

        /// <summary>
        /// Initializes a new instance of the <see cref="UserRestrictionProvider"/> class.
        /// </summary>
        /// <param name="parent">The parent type description provider.</param>
        private UserRestrictionProvider(TypeDescriptionProvider parent) : base(parent)
        {
            this.parent = parent;
        }

        /// <summary>
        /// Gets a custom type descriptor for the given type and object.
        /// </summary>
        /// <param name="objectType">The type of object for which to retrieve the type descriptor.</param>
        /// <param name="instance">An instance of the type. Can be null if no instance was passed to the <see cref="T:System.ComponentModel.TypeDescriptor"/>.</param>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.ICustomTypeDescriptor"/> that can provide metadata for the type.
        /// </returns>
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            ModelElement e = instance as ModelElement;
            if ( e != null )
                return new RestrictedShapeTypeDescriptor(GetRestrictionMode(e.Store), instance, parent);
               
            return parent.GetTypeDescriptor(objectType, instance);
        }

        #region register providers
        /// <summary>
        /// Registers the restricted element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public static void RegisterRestrictedElement<T>() where T : ModelElement
        {
            RegisterRestrictedElement(typeof (T));
        }
        /// <summary>
        /// Registers all restricted elements in the Assembly of the given Diagram type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public static void RegisterAllRestrictedElements<T>() where T : Diagram
        {
            Assembly a = typeof(T).Assembly;
            foreach (Type type in a.GetTypes())
            {
                // if any of the following aatributes are present:
                object[] attributes1 = type.GetCustomAttributes(typeof(RestrictedPropertyAttribute), true);
                object[] attributes2 = type.GetCustomAttributes(typeof(CustomRestrictedPropertyAttribute), true);
                if (attributes1.Length + attributes2.Length > 0)
                    RegisterRestrictedElement(type);
            }
        }
        private static void RegisterRestrictedElement(Type t) 
        {
            TypeDescriptionProvider baseprovider = TypeDescriptor.GetProvider(t);
            TypeDescriptor.AddProvider(new UserRestrictionProvider(baseprovider), t);
        }
        #endregion

        #region get and set restriction modes
        private static readonly Dictionary<Guid, int> restrictions = new Dictionary<Guid, int>();
        
        /// <summary>
        /// Gets the restriction mode for the given store (and so for the diagram / model of this store)..
        /// </summary>
        /// <param name="store">The store of the diagram or model or modelelement.</param>
        /// <returns>The restriction value, if the store is unknown by the UserRestrictionProvider 0 will be returend.</returns>
        public static int GetRestrictionMode(Store store)
        {
            if (restrictions.ContainsKey(store.Id))
                return restrictions[store.Id];

            return 0;
        }

        /// <summary>
        /// Sets the restriction mode for the given store (and so for the diagram / model of this store).
        /// </summary>
        /// <param name="store">The store of the diagram or model or modelelement.</param>
        /// <param name="restriction">The restriction value.</param>
        public static void SetRestrictionMode(Store store,int restriction)
        {
            restrictions[store.Id] = restriction;
        }
        #endregion

        #region AllowMerge for compartment item add
        private static readonly Dictionary<string, bool> cache = new Dictionary<string, bool>();
        public static bool AllowMerge(ModelElement element, string compartmentToAdd)
        {
            int restrictionMode = GetRestrictionMode(element.Store);
            string cacheKey = element.Store.Id + compartmentToAdd + element.GetType().FullName + restrictionMode;

            if (cache.ContainsKey(cacheKey))
                return cache[cacheKey];

            bool allow = true;
            foreach (RestrictElementMergeAttribute attribute in element.GetType().GetCustomAttributes(typeof(RestrictElementMergeAttribute), true))
            {
                // the attribute matches the current restrictionMode and the compartment
                if (attribute.RestrictionMode == restrictionMode
                    && attribute.Element == compartmentToAdd)
                {
                    allow = !attribute.IsRestricted;
                    break;
                }
            }

            cache.Add(cacheKey, allow);
            return allow;
        }
        #endregion
    }
}
